//
//  ECSBlockInvocation.m
//  ECS DevelopKit
//
//  Created by LittoCats on 8/29/14.
//  Copyright (c) 2014 Littocats. All rights reserved.
//

#import "ECSBlockInvocation.h"

#import <objc/runtime.h>

struct ECSBlockDescriptor {
    unsigned long reserved;
    unsigned long size;
    void *rest[1];
};

struct ECSBlock {
    void *isa;
    int flags;
    int reserved;
    void *invoke;
    struct ECSBlockDescriptor *descriptor;
};

static const char *ECSGetBlockSignature(id blockObj)
{
    struct ECSBlock *block = (__bridge void *)blockObj;
    struct ECSBlockDescriptor *descriptor = block->descriptor;
    
    int copyDisposeFlag = 1 << 25;
    int signatureFlag = 1 << 30;
    
    assert(block->flags & signatureFlag);
    
    int index = 0;
    if(block->flags & copyDisposeFlag)
        index += 2;
    
    return descriptor->rest[index];
}

@implementation ECSBlockInvocation

+ (id)invokeBlock:(id)block withArguments:(NSArray *)arguments
{
    return [self invokeBlock:block withArguments:arguments returnType:NULL];
}

+ (id)invokeBlock:(id)block withArguments:(NSArray *)arguments returnType:(const char **)type
{
    if (![block isKindOfClass:NSClassFromString(@"NSBlock")])
        @throw [[NSException alloc] initWithName:@"ECSBlockInvocation" reason:@"Object < %@ > is invalid block ." userInfo:nil];
    
    const char *blocksig = ECSGetBlockSignature(block);
    NSMutableString *encodeType = [NSMutableString new];
    [encodeType appendFormat:@"%c%i@?0",blocksig[0],4*((int)arguments.count +1)];
    for (int index = 1; index <= arguments.count + 1; index ++) {
        [encodeType appendFormat:@"@%i",index * 4];
    }
    
    
    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:[encodeType UTF8String]];
    NSInvocation *invo = [NSInvocation invocationWithMethodSignature:signature];
    
    for (int index = 0; index < arguments.count;) {
        id argument = arguments[index ++];
        [invo setArgument:&argument atIndex:index];
    }

    [invo retainArguments];
    [invo setTarget:block];
    [invo invoke];
    
    const char *returnType = [signature methodReturnType];
    if (returnType[0] == 'v') {
        return nil;
    }
    
    void *buffer;
    id result;
    
    if (returnType[0] == _C_ID || returnType[0] == _C_CLASS) {
        [invo getReturnValue:&buffer];
        result = (__bridge id)(buffer);
    }else{
        buffer = (void *)malloc([signature methodReturnLength]);
        [invo getReturnValue:buffer];
        if (returnType[0] == _C_CHR || returnType[0] == _C_UCHR){
            char *c = buffer;
            result = @(c[0]);
        }else if (returnType[0] == _C_SHT || returnType[0] == _C_USHT){
            const char *s = buffer;
            result = [[NSString alloc] initWithUTF8String:s];
        }else if (returnType[0] == _C_INT){
            int *i = buffer;
            result = @(*i);
        }else if (returnType[0] == _C_UINT){
            unsigned int *i = buffer;
            result = @(*i);
        }else if (returnType[0] == _C_FLT){
            float *f = buffer;
            result = @(*f);
        }else if (returnType[0] == _C_LNG){
            long *num = buffer;
            result = @(*num);
        }else if (returnType[0] == _C_ULNG){
            long *num = buffer;
            result = @(*num);
        }else if (returnType[0] == _C_LNG_LNG){
            long long *num = buffer;
            result = @(*num);
        }else if (returnType[0] == _C_ULNG_LNG){
            long long *num = buffer;
            result = @(*num);
        }else if (returnType[0] == _C_DBL){
            double *num = buffer;
            result = @(*num);
        }else if (returnType[0] == _C_BOOL){
            BOOL *b = buffer;
            result = @(*b);
        }else if (returnType[0] == _C_SEL){
            SEL *s = buffer;
            result = NSStringFromSelector(*s);
        }else{
            @throw [[NSException alloc] initWithName:@"[ NSObject+EC ] return type unsuport error" reason:nil userInfo:nil];
        }
        free(buffer);
    }
    
    return result;
}

@end
